package gui;

import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import calculate.Add;
import calculate.BinaryOperator;
import calculate.Division;
import calculate.Minus;
import calculate.Multiply;
import calculate.Negative;
import calculate.Percentage;
import calculate.Reciprocal;
import calculate.Square;
import calculate.Square_root;
import calculate.UnaryOperator;

/**
 * This class implements the simple version of the calculator which provides
 * more supports: basic and advanced calculation(+,-,*,/;square, square root,
 * negative, percentage, reciprocal), illegal input detection and defense,
 * functional keys
 * 
 * @author Jacky Xu
 *
 */
public class SimpleCalculator extends Calculator {
	private double pre_operand = 0;
	// if the system is locked down
	private boolean lockdown = false;

	// if the dot button has already been pressed
	private boolean flag = true;

	// new-added the error detection item
	private final List<String> errors = new ArrayList<String>(Arrays.asList("NaN", "Infinity"));

	public SimpleCalculator() {
		super();

		JFrame frame = new JFrame("Calculator");
		frame.setBounds(700, 300, 450, 650);

		GridBagLayout gbaglayout = new GridBagLayout(); // GridBagLayout
		GridBagConstraints constraints = new GridBagConstraints();
		frame.setLayout(gbaglayout);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		constraints.fill = GridBagConstraints.BOTH;
		constraints.weightx = 0.0;
		constraints.gridwidth = GridBagConstraints.REMAINDER;

		setConstrain(constraints, 0, 0, 4, 1, 0.5, 0.5);
		gbaglayout.setConstraints(line1, constraints);
		frame.add(line1);

		setConstrain(constraints, 0, 1, 4, 2, 0.5, 0.5);
		gbaglayout.setConstraints(line2, constraints);
		frame.add(line2);

		setConstrain(constraints, 0, 3, 4, 6, 2, 2);
		gbaglayout.setConstraints(this.keyboard, constraints);
		frame.add(this.keyboard);

		frame.setVisible(true);

	}

	@Override
	protected JPanel getKeyboard(Font f) {
		JPanel keyboard = new JPanel();
		keyboard.setLayout(new GridLayout(6, 4, 0, 0));

		for (String key : this.keys) {
			JButton button = new JButton(key);
			button.setFont(f);
			button.addActionListener(e -> {
				String command = e.getActionCommand();
				Parser(command);
			});
			keyboard.add(button);
		}

		return keyboard;
	}

	@Override
	protected HashMap<String, BinaryOperator> getBiOperator() {
		HashMap<String, BinaryOperator> bio = new HashMap<String, BinaryOperator>();
		bio.put("-", new Minus());
		bio.put("+", new Add());
		bio.put("×", new Multiply());
		bio.put("÷", new Division());

		return bio;
	}

	@Override
	protected HashMap<String, UnaryOperator> getUnOperator() {
		HashMap<String, UnaryOperator> uo = new HashMap<String, UnaryOperator>();
		uo.put("%", new Percentage());
		uo.put("sqrt", new Square_root());
		uo.put("x^2", new Square());
		uo.put("+/-", new Negative());
		uo.put("1/x", new Reciprocal());
		this.unaryoutput.put("1/x", "1/");
		this.unaryoutput.put("x^2", "sqr");
		this.unaryoutput.put("sqrt", "sqrt");
		this.unaryoutput.put("+/-", "negate");
		this.unaryoutput.put("%", "");

		return uo;
	}

	@Override
	protected List<String> getFunctional() {
		return new ArrayList<String>(Arrays.asList("Back", "C", "CE"));
	}

	@Override
	protected String[] getKeys() {
		String[] keys = { "%", "CE", "C", "Back", "1/x", "x^2", "sqrt", "÷", "7", "8", "9", "×", "4", "5", "6", "-",
				"1", "2", "3", "+", "+/-", "0", ".", "=" };

		return keys;
	}

	@Override
	protected void Parser(String command) {
		if (this.lockdown) {
			if (command.equals("C") || command.equals("CE")) {
				buffer = "";
				pre_operand = 0;
				pre_opt = "";
				lockdown = false;
				flag = true;

				this.line1.setText("");
				this.line2.setText("0");
			}

			return;
		}

		if (this.numbers.contains(command)) {
			this.buffer += command;
			this.line2.setText(buffer);
		} else if (command.equals(".")) {
			if (this.flag) {
				this.buffer += command;
				this.line2.setText(buffer);
				this.flag = false;
			}
		} else if (this.unaryoperators.keySet().contains(command)) {
			UnaryOperator ops = this.unaryoperators.get(command);

			double a = buffer.equals("") ? pre_operand : Double.valueOf(buffer);

			double result = ops.calculate(a);

			if (this.errors.contains(String.valueOf(result))) {
				this.lockdown = true;
				this.line1.setText("");
				this.line2.setText("Math Error!");
				return;
			}

			this.pre_operand = result;

			String ret = this.unaryoutput.get(command) + "(" + String.valueOf(a) + ")";
			buffer = "";
			flag = true;
			this.line1.setText(ret);
			this.line2.setText(String.valueOf(result));
		} else if (this.binaryoperators.keySet().contains(command)) {
			if (buffer.equals("")) {
				this.pre_opt = command;
				this.line1.setText(String.valueOf(this.pre_operand) + command);
			} else {
				if (this.pre_opt.equals("")) {
					this.pre_opt = command;
					this.pre_operand = Double.valueOf(buffer);
					buffer = "";
					flag = true;
					this.line1.setText(String.valueOf(this.pre_operand) + command);
				} else {
					BinaryOperator opt = binaryoperators.get(this.pre_opt);
					this.pre_opt = command;
					double a = Double.valueOf(buffer);
					buffer = "";
					flag = true;
					double result = opt.calculate(pre_operand, a);

					if (this.errors.contains(String.valueOf(result))) {
						this.lockdown = true;
						this.line1.setText("");
						this.line2.setText("Math Error!");
						return;
					}

					this.pre_operand = result;
					this.line1.setText(String.valueOf(result) + command);
					this.line2.setText(String.valueOf(result));
				}
			}
		} else if (command.equals("=")) {
			if (buffer.equals("")) {
				this.pre_opt = "";
				this.line1.setText(String.valueOf(this.pre_operand) + "=");
				this.line2.setText(String.valueOf(this.pre_operand));
			} else {
				double a = buffer.equals(".") ? 0 : Double.valueOf(buffer);
				buffer = "";
				flag = true;

				if (this.pre_opt.contentEquals("")) {
					this.pre_operand = a;
					this.line1.setText(String.valueOf(pre_operand) + "=");
					this.line2.setText(String.valueOf(a));

				} else {

					double result = this.binaryoperators.get(this.pre_opt).calculate(this.pre_operand, a);

					if (this.errors.contains(String.valueOf(result))) {
						this.lockdown = true;
						this.line1.setText("");
						this.line2.setText("Math Error!");
						return;
					}

					this.line1.setText(String.valueOf(pre_operand) + pre_opt + String.valueOf(a) + "=");
					this.line2.setText(String.valueOf(result));

					this.pre_opt = "";
					this.pre_operand = result;
				}

			}
		} else if (this.functional.contains(command)) {
			if (command.equals("C") || command.equals("CE")) {
				buffer = "";
				pre_operand = 0;
				pre_opt = "";
				lockdown = false;
				flag = true;

				this.line1.setText("");
				this.line2.setText("0");
			} else if (command.equals("Back")) {
				if (!buffer.equals("")) {
					buffer = buffer.substring(0, buffer.length() - 1);
					this.line2.setText(buffer);
				}
			}
		}
	}

	public static void main(String[] args) {
		new SimpleCalculator();
		System.out.println("Hello World!");
	}

}
